/*
 * radius.c
 *
 *  Created on: 2014年9月28日
 *      Author: LiJie
 */

#include <errno.h>
#include <stdio.h>
#include <netdb.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include "md5.h"
#include "log.h"
#include "radclt.h"

#include "devid.h"

struct interface_t itfs[MAX_IF_COUNT];


static int radclt_set_nonblock(int fd);
static void radmsg_acct_insert_auth(radclt_t *h, radmsg_t *m);

/*
 *
 */
radclt_t *radclt_open(uint32_t type, const char *host, uint16_t port,
		const char *secret, const char *nasip, uint32_t msto, int nonblock)
{
	int flag = 1;
	radclt_t *h = NULL;
	uint32_t ip = 0;
	struct sockaddr_in sin;

	if (!host || !secret || msto < 0) {
		DEBUG("ARGS error");
		return NULL;
	}
	if ((type != RADCLT_AUTH) \
			&& (type != RADCLT_ACCT) \
			&& (type != RADCLT_DROP)) {
		DEBUG("Unknown radius client handle type=%d", type);
		return NULL;
	}
	if ((RADCLT_AUTH == type || RADCLT_ACCT == type) && !nasip) {
		DEBUG("Radius client handle type is ACCT or AUTH, NAS address can not be null");
		return NULL;
	}
	if (type != RADCLT_DROP) {
		if (port > 65535 || port < 1) {
			DEBUG("Port=%d error", port);
			return NULL;
		}
	}


	if (nasip) {
		ip = inet_addr(nasip);
/*
		if (0 == ip || 0xFFFFFFFF == ip) {
                    strncpy(nasip,itfs[0].nac_ip,strlen(itfs[0].nac_ip));
			//xyz_log_error("NAS IP is error");
			//return NULL;
		}
*/
	}


	h = (radclt_t *)calloc(1, sizeof(radclt_t));
	if (!h) {
		DEBUG("calloc : %s", strerror(errno));
		return NULL;
	}
	h->type = type;
	if (pthread_mutex_init(&(h->idlock), NULL)) {
		DEBUG("Init mutex lock error");
		radclt_close(&h);
		return NULL;
	}
	h->ident = random();
	//  解析Radius服务器地址
	memset(&(h->addr), 0, sizeof(h->addr));
	h->addr.sin_family = AF_INET;
	if (!inet_aton(host, &(h->addr.sin_addr))) {
		struct hostent *hent = NULL;
		if ((hent = gethostbyname(host)) == NULL) {
			DEBUG("The host \"%s\" not found", host);
			radclt_close(&h);
			return NULL;
		}
		memcpy(&(h->addr.sin_addr), hent->h_addr, sizeof(h->addr.sin_addr));
	}
	if (type != RADCLT_DROP) {
		h->addr.sin_port = htons(port);
	} else {
		h->addr.sin_port = htons(0);
	}
	//  处理共享密钥
	if (!(h->secret = strdup(secret))) {
		DEBUG("strdup : %s", strerror(errno));
		radclt_close(&h);
		return NULL;
	}
	h->secretlen = strlen(secret);
	h->timeout = msto;

	if ((h->fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
		DEBUG("socket: %s", strerror(errno));
		radclt_close(&h);
		return NULL;
	}
	//  设置为非阻塞模式
	if (1 == nonblock) {
		if(radclt_set_nonblock(h->fd) < 0){
			radclt_close(&h);
			return NULL;
		}
	} else {
		struct timeval tv;
		tv.tv_sec = msto / 1000;
		tv.tv_usec = (msto % 1000) * 1000;
		if (setsockopt(h->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
			DEBUG("setsockopt : %s", strerror(errno));
			radclt_close(&h);
			return NULL;
		}
	}
	//
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
	if (RADCLT_DROP == type) {
		sin.sin_port = htons(port);
	} else {
		sin.sin_port = htons(0);
	}
	if (bind(h->fd, (const struct sockaddr *)&(sin), sizeof(sin)) == -1) {
		DEBUG("bind : %s", strerror(errno));
		radclt_close(&h);
		return NULL;
	}
#if 0
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = inet_addr(nasip);
	if (RADCLT_DROP == type) {
		sin.sin_port = htons(port);
	} else {
		sin.sin_port = htons(0);
	}
	if (bind(h->fd, (const struct sockaddr *)&(sin), sizeof(sin)) == -1) {
		xyz_log_warn("bind : %s", strerror(errno));
		radclt_close(&h);
		return NULL;
		//	绑定发送数据接口
		do {
			struct ifreq intf;
			strncpy(intf.ifr_name, device, IFNAMSIZ);
			if (setsockopt(h->fd, SOL_SOCKET, SO_BINDTODEVICE, (char *) &intf, sizeof(intf)) == 0) {
				//	绑定成功
				flag = 0;
			}
		} while(flag);
	}
#endif
	h->nasip = ip;
	return h;
}

/*
 * 销毁Radius句柄
 */
void radclt_close(radclt_t **h)
{
	if (h && *h) {
		if ((*h)->fd > 0) {
			close((*h)->fd);
		}
		if ((*h)->secret) {
			free((*h)->secret);
		}
		pthread_mutex_destroy(&((*h)->idlock));
		free((*h));
		(*h) = NULL;
	}
}

/*
 *  Response coa package
 */
int radclt_coa_rep(radclt_t *h, uint8_t code, radmsg_t *m, struct sockaddr_in *dst)
{
	int rc = 0;

	if (!h || !m || !dst) {
		DEBUG("ARGS error");
		return -1;
	}
	if (h->type != RADCLT_DROP) {
		DEBUG("This handle type is not CoA");
		return -1;
	}
	m->buf[POS_CODE] = code;
	if (radmsg_calc_auth(h, m->auth, m)) {
		DEBUG("Calc AUTH error");
		return -1;
	}
	memcpy(&(m->buf[POS_AUTH]), m->auth, MD5_DIGEST_LENGTH);
	rc = sendto(h->fd, m->buf, m->off, 0, (struct sockaddr *)dst, sizeof(struct sockaddr_in));
	if (rc < 1) {
		DEBUG("sendto : %s", strerror(errno));
		return -1;
	}
	return 0;
}


/*
 * 回复DM下线报文
 */
int radclt_drop_rep(radclt_t *h, uint8_t code, radmsg_t *m, struct sockaddr_in *dst)
{
	int rc = 0;

	if (!h || !m || !dst) {
		DEBUG("ARGS error");
		return -1;
	}
	if (h->type != RADCLT_DROP) {
		DEBUG("This handle type is not DM-HANDLE");
		return -1;
	}
	m->buf[POS_CODE] = code;
	if (radmsg_calc_auth(h, m->auth, m)) {
		DEBUG("Calc AUTH error");
		return -1;
	}
	memcpy(&(m->buf[POS_AUTH]), m->auth, MD5_DIGEST_LENGTH);
	rc = sendto(h->fd, m->buf, m->off, 0, (struct sockaddr *)dst, sizeof(struct sockaddr_in));
	if (rc < 1) {
		DEBUG("sendto : %s", strerror(errno));
		return -1;
	}
	return 0;
}

/*
 *	发送消息
 */
int radclt_send(radclt_t *h, radmsg_t *m)
{
	int len = 0;
	if (!h || !m) {
		DEBUG("ARGS error");
		return -1;
	}
	if (h->fd < 1 || m->off < 1 || m->flag != RADMSG_OUT) {
		DEBUG("Send conditions are not complete");
		return -1;
	}
	m->buf[POS_LENGTH] = m->off >> 8 & 0xFF;
	m->buf[POS_LENGTH+1] = m->off & 0xFF;

	if (RADCLT_ACCT == h->type) {
		radmsg_acct_insert_auth(h, m);
	}
	len = sendto(h->fd, m->buf, m->off, 0,(const struct sockaddr *)&(h->addr), sizeof(h->addr));
	if (0 > len) {
		DEBUG("sendto : %s", strerror(errno));
		return -1;
	}
	return 0;
}

/*
 * Radius接收消息
 */
int radclt_recv(radclt_t *h, radmsg_t *m)
{
	socklen_t fromlen = sizeof(struct sockaddr_in);
	int len = 0;
	if (!h || !m) {
		DEBUG("ARGS error");
		return -1;
	}
	len = recvfrom(h->fd, m->buf, MSGSIZE, 0, (struct sockaddr *)&m->from, &fromlen);
	if (len < 1) {
		DEBUG("recvfrom : %s", strerror(errno));
		return -1;
	}
	m->flag = RADMSG_IN;
	//  获取消息长度
	m->len = m->buf[POS_LENGTH] << 8 | m->buf[POS_LENGTH + 1];
	if (len < m->len) {
		DEBUG("The actual length is less than the given length");
		return -1;
	}
	m->off = POS_ATTRS;
	//  获取消息code
	m->code = m->buf[POS_CODE];
	//  获取消息ID
	m->ident = m->buf[POS_IDENT];
	//  获取消息AUTH
	memcpy(m->auth, &(m->buf[POS_AUTH]), LEN_AUTH);
	return 0;
}

/////////////////////////////////////////////////////////////////////////////

/*
 * 计算消息的认证字AUTH
 * 初始AUTH为全0
 */
int radmsg_calc_auth(radclt_t *h, uint8_t *auth, radmsg_t *m)
{
	MD5_CTX ctx;
	unsigned char md5[MD5_DIGEST_LENGTH] = { 0 };

	if (!h || !auth || !m) {
		DEBUG("ARGS error");
		return -1;
	}
	MD5Init(&ctx);
	MD5Update(&ctx, &(m->buf[POS_CODE]), POS_AUTH - POS_CODE);
	MD5Update(&ctx, auth, LEN_AUTH);
	MD5Update(&ctx, &(m->buf[POS_ATTRS]), ((m->len) - POS_ATTRS));
	MD5Update(&ctx, h->secret, h->secretlen);
	MD5Final(md5, &ctx);
	memcpy(auth, md5, MD5_DIGEST_LENGTH);
	return 0;
}

/*
 * 验证请求消息是否合法
 * 该消息为Radius服务器发来
 */
int radmsg_is_valid_req(radclt_t *h, radmsg_t *m)
{
	char auth[MD5_DIGEST_LENGTH] = { 0 };

	if (!h || !m) {
		DEBUG("ARGS error");
		return -1;
	}
	if (radmsg_calc_auth(h, auth, m)) {
		DEBUG("Calc AUTH error");
		return -1;
	}
	if (memcmp(auth, m->auth, MD5_DIGEST_LENGTH)) {
		return -1;
	}
	return 0;
}

/*
 * 验证收到的消息正确性
 * 参数auth为发送时产生的AUTH
 */
int radmsg_is_valid_rep(radclt_t *h, uint8_t *auth, radmsg_t *m)
{
	MD5_CTX ctx;
	unsigned char md5[MD5_DIGEST_LENGTH] = { 0 };

	if (!h || !auth || !m) {
		DEBUG("ARGS error");
		return -1;
	}
	MD5Init(&ctx);
	MD5Update(&ctx, &(m->buf[POS_CODE]), POS_AUTH - POS_CODE);
	MD5Update(&ctx, auth, LEN_AUTH);
	MD5Update(&ctx, &(m->buf[POS_ATTRS]), ((m->len) - POS_ATTRS));
	MD5Update(&ctx, h->secret, h->secretlen);
	MD5Final(md5, &ctx);
	if (memcmp(&(m->buf[POS_AUTH]), md5, sizeof(md5))) {
		return -1;
	}
	return 0;
}

/*
 * 返回属性类型. 如果没有属性则返回0. 失败则返回-1.
 */
int radmsg_get_attr(radmsg_t *m, void **value, int *len)
{
	int type = 0;
	if (m->off >= m->len || m->flag != RADMSG_IN) {
		return 0;
	}
	//  检测是否循环到边界
	if (m->off + 2 > m->len) {
		DEBUG("Malformed attribute in response");
		return -1;
	}
	//  type
	type = m->buf[m->off++];
	//  length
	*len = (m->buf[m->off++]) - 2;
	//  value
	if (m->off + (int)(*len) > m->len) {
		DEBUG("Malformed attribute in response");
		return -1;
	}
	*value = &(m->buf[m->off]);
	m->off += *len;
	return type;
}
/*
 * 解析厂商私有属性,返回属性类型
 * 	 0                   1                   2                   3
 *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |     Type      |  Length       |            Vendor-Id
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *       Vendor-Id (cont)           | Vendor type   | Vendor length |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |   Attribute-Specific...
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
 *  type : 26 为厂商私有属性
 * 如果没有属性则返回0.
 * 失败则返回-1.
 *  注意：第一次需要把attr memset为0
 */
int radmsg_get_vendor_attr(uint8_t *buf, int len, radven_t *attr)
{
	if (!buf || !attr || len < 7) {
		DEBUG("Args error");
		return -1;
	}

	if (!(attr->flag)) {
		attr->ident = (buf[0] << 24 & 0xFF000000)\
						| (buf[1] << 16 & 0xFF0000)\
						| (buf[2] << 8 & 0xFF00)\
						| (buf[3] & 0xFF);
		attr->off = 4;
		attr->flag = 1;
	} else {
		if (attr->off > len) {
			return -1;
		}
		if (attr->off == len) {
			return 0;
		}
	}

	attr->type = buf[attr->off++];
	attr->len = (buf[attr->off++]) - 2;
	if (attr->off + attr->len > len) {
		DEBUG("Vendor %d self attribute type %d length %d bytes is error",
				attr->ident, attr->type, attr->len);
		return -1;
	}
	attr->value = &(buf[attr->off]);
	attr->off += attr->len;
	return attr->type;
}
/*
 * 初始化请求消息
 */
int radmsg_init_req(radclt_t *h, radmsg_t *m, uint8_t code)
{
	int i = 0;

	//  添加消息code
	m->buf[POS_CODE] = code;
	//  添加消息ID
	pthread_mutex_lock(&(h->idlock));
	m->buf[POS_IDENT] = ++(h->ident);
	pthread_mutex_unlock(&(h->idlock));
	if (RAD_ACCESS_REQUEST == code) {
		//  生成authenticator
		for (i = 0;  i < LEN_AUTH;  i += 2) {
			long r = 0;
			r = random();
			m->buf[POS_AUTH+i] = (u_char)r;
			m->buf[POS_AUTH+i+1] = (u_char)(r >> 8);
		}
	} else {
		memset(&(m->buf[POS_AUTH]), 0, LEN_AUTH);
	}
	//  当前输出消息偏移
	m->off = POS_ATTRS;
	//  authenticator偏移
//	m->authpos = 0;
	//  请求消息函数已调用
	m->flag = RADMSG_OUT;
	return 0;
}


int radmsg_put_uint8(radmsg_t *m, uint8_t type, uint8_t v)
{
	if (!m ) {
		DEBUG("ARGS error");
		return -1;
	}
	if (m->flag != RADMSG_OUT || m->off + 3 > MSGSIZE) {
		DEBUG("Radius client message type error or buffer will overflow");
		return -1;
	}
	m->buf[m->off++] = type;
	m->buf[m->off++] = 1 + 2;
	m->buf[m->off++] = v;
	return 0;
}

int radmsg_put_uint16(radmsg_t *m, uint8_t type, uint16_t v)
{
	if (!m ) {
		DEBUG("ARGS error");
		return -1;
	}
	if (m->flag != RADMSG_OUT || m->off + 4 > MSGSIZE) {
		DEBUG("Radius client message type error or buffer will overflow");
		return -1;
	}
	m->buf[m->off++] = type;
	m->buf[m->off++] = 2 + 2;
	m->buf[m->off++] = v >> 8 & 0xFF;
	m->buf[m->off++] = v & 0xFF;
	return 0;
}

int radmsg_put_uint32(radmsg_t *m, uint8_t type, uint32_t v)
{
	if (!m ) {
		DEBUG("ARGS error");
		return -1;
	}
	if (m->flag != RADMSG_OUT || m->off + 6 > MSGSIZE) {
		DEBUG("Radius client message type error or buffer will overflow");
		return -1;
	}
	m->buf[m->off++] = type;
	m->buf[m->off++] = 4 + 2;
	m->buf[m->off++] = v >> 24 & 0xFF;
	m->buf[m->off++] = v >> 16 & 0xFF;
	m->buf[m->off++] = v >> 8 & 0xFF;
	m->buf[m->off++] = v & 0xFF;
	return 0;
}

int radmsg_put_v4ip(radmsg_t *m, uint8_t type, uint32_t ip)
{
	if (!m ) {
		DEBUG("ARGS error");
		return -1;
	}
	if (m->flag != RADMSG_OUT || m->off + 6 > MSGSIZE) {
		DEBUG("Radius client message type error or buffer will overflow");
		return -1;
	}
	m->buf[m->off++] = type;
	m->buf[m->off++] = 4 + 2;
	memcpy(&(m->buf[m->off]), &ip, 4);
	m->off += 4;
	return 0;
}

int radmsg_put_str(radmsg_t *m, uint8_t type, const char *v, int len)
{
	if (!m || !v || len > RAD_MAX_ATTR_LEN) {//253
		DEBUG("ARGS error");
		return -1;
	}
	if ((m->flag != RADMSG_OUT) || (m->off + len + 2 > MSGSIZE)) {//4096
		DEBUG("Radius client message type error or buffer will overflow");
		return -1;
	}
	m->buf[m->off++] = type;
	m->buf[m->off++] = len + 2;
	memcpy(&(m->buf[m->off]), v, len);
	m->off += len;
	return 0;
}

/*
 * 添加用户PAP密码
 */
int radmsg_put_pap_pwd(radclt_t *h, radmsg_t *m, const char *pwd, int len)
{
	MD5_CTX ctx;
	unsigned char md5[MD5_DIGEST_LENGTH] = { 0 };
	int paddedlen = 0;
	int padlen = 0;
	int pos = 0;
	int i = 0;

	if (!m || !h || !pwd) {
		DEBUG("ARGS error");
		return -1;
	}
	if (m->flag != RADMSG_OUT) {
		DEBUG("Radius client message type error");
		return -1;
	}
	//  检测密码是否大于128字节
	if (len > PASSSIZE) {
		len = PASSSIZE;
	}

	//  计算密码需要总长度
	paddedlen = (len == 0 ? 16 : (len+15) & ~0xf);
	//  填充长度
	padlen = paddedlen - len;
	if (m->off + paddedlen + 2 > MSGSIZE) {
		DEBUG("Radius client message buffer is overflow");
		return -1;
	}

	m->buf[m->off++] = RAD_USER_PASSWORD;
	m->buf[m->off++] = paddedlen + 2;

	memcpy(md5, &(m->buf[POS_AUTH]), LEN_AUTH);

	for (pos = 0; pos < paddedlen; pos += 16) {
		MD5Init(&ctx);
		MD5Update(&ctx, h->secret, h->secretlen);
		MD5Update(&ctx, md5, MD5_DIGEST_LENGTH);
		MD5Final(md5, &ctx);
		for (i = 0; i < 16; i++) {
			m->buf[m->off + pos + i] = (md5[i] ^= pwd[pos + i]);
		}
	}
	m->off += paddedlen;
	return 0;
}

///////////////////////////////////////////////////////////////////////
/*
 * 设置fd为非阻塞模式
 */
static int radclt_set_nonblock(int fd)
{
	int flags;

	if (fd < 0) {
		return -1;
	}
	if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
		DEBUG("fcntl : %s", strerror(errno));
		return -1;
	} else if (fcntl(fd, F_SETFL, flags | O_NONBLOCK | O_ASYNC) != 0) {
		DEBUG("fcntl : %s", strerror(errno));
		return -1;
	}
	return 0;
}

/*
 * 计费包插入鉴别码
 */
static void radmsg_acct_insert_auth(radclt_t *h, radmsg_t *m)
{
	MD5_CTX ctx;
	unsigned char md5[MD5_DIGEST_LENGTH] = { 0 };

	MD5Init(&ctx);
	MD5Update(&ctx, &(m->buf[POS_CODE]), m->off - POS_CODE);
	MD5Update(&ctx, h->secret, h->secretlen);
	MD5Final(md5, &ctx);
	memcpy(&(m->buf[POS_AUTH]), md5, MD5_DIGEST_LENGTH);
}
